
//
//  CPJSQLiteManager.swift
//  数据库的使用_Swift
//
//  Created by YYY on 2017/9/8.
//  Copyright © 2017年 成品家（北京）网路科技有限公司. All rights reserved.
//

/*
 实时敏感度不高
 更新频率低
 有查询需求的
 适合做数据缓存
 */
import Foundation

import FMDB

//最大的数据库缓存时间(清除前五天的数据)
private let maxDBCacheTime:TimeInterval = -60 //-5 * 24 * 60 * 60

/*
 SQLite管理器
 1:数据库本质是保存在沙盒的一个文件，首先需要创建并打开数据库
 2:数据库队列
 */
class CPJSQLiteManager
{
    //数据库队列
    let queue: FMDatabaseQueue
    
    //全局数据库工具访问点
    //单例
    static let shared = CPJSQLiteManager()
    
    //构造函数
    private init()
    {
        //数据库全路径
        //数据库名
        let dataBaseName = "sinaweibo.db"
        //沙盒路径
        let documentPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
        let path = (documentPath as NSString).appendingPathComponent(dataBaseName)
        
        print("数据库存储路径：\(path)")
        
        //创建数据库队列(创建或者打开数据库)
        queue = FMDatabaseQueue(path: path)
        
        //打开数据库
        createTable()
        
        
        //注册通知，监听应用程序进入后台
        NotificationCenter.default.addObserver(self, selector: #selector(clearCache), name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil)
    }
    
    //注销通知
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    //清除数据缓存
    //SQLite的数据不断增加，数据库文件的大小，不断增加。
    //如果删除了数据，数据库的大小，不会变小。
    @objc private func clearCache()
    {
        let dateString = Date.sinaDateConvertToString(interval: maxDBCacheTime)
        
        print("清除缓存数据 \(dateString)")
        
        //准备sql
        let sql = "DELETE FROM Main_Weibo WHERE createTime < ?;"
        
        //执行sql
        queue.inDatabase { (db) in
            if db?.executeUpdate(sql, withArgumentsIn: [dateString]) == true
            {
                print("删除了\(db?.changes())条记录")
            }
            
        }
    }
}

private extension CPJSQLiteManager
{
    //创建数据表
    func createTable()
    {
        guard let path = Bundle.main.path(forResource: "weibo.sql", ofType: nil),
            let sql = try? String(contentsOfFile: path) else
        {
            return
        }
        
        //执行SQL
        //FMDB的内部队列是串行队列，同步执行
        //可以保证同一时间，只有一个任务操作数据库，从而保证数据库的读写安全
        //executeStatements:只有在创建表的时候，使用执行多条语句，可以一次创建多个数据表。
        //在执行增删改查的时候，一定不要使用executeStatements方法，否则可能会被注入。
        queue.inDatabase { (db) in
            if db?.executeStatements(sql) == true
            {
                print("创表成功")
            }
            else
            {
                print("创表失败")
            }
        }
        
        print("over")
    }
}

//新增或者修改数据表
extension CPJSQLiteManager
{
    func updateTable(userId: String, array: [[String: AnyObject]])
    {
        //准备sql
        /*
         statusId：微博ID
         userId：用户ID
         status：微博json
         */
    
        let sql = "INSERT OR REPLACE INTO Main_Weibo (statusId, userId, status) VALUES(?, ?, ?);"
        
        
        //执行sql
        queue.inTransaction { (db, rollBack) in
            
            //遍历数组，逐条插入微博数据
            //从字典取得微博代号，将字典序列化成二进制数据
            for dict in array
            {
                
                guard let statusId = dict["idstr"],
                let jsonData = try? JSONSerialization.data(withJSONObject: dict, options: []) else
                {
                    continue
                }
                
                let result = db?.executeUpdate(sql, withArgumentsIn: [statusId, userId, jsonData])
                if result == false
                {
                    //需要回滚
                    rollBack?.pointee = true
                    break
                }
                
                
                print("插入成功")
                
            }
        }
    }
}

extension CPJSQLiteManager
{
    func queryTable(sql: String) -> [[String: AnyObject]]
    {
        //结果数组
        var result = [[String: AnyObject]]()
        
        //执行sql
        //查询数据，不会修改数据，不需要开启事务
        //事务的目的：为了保证在数据的有效性，一旦失败，回滚到初始状态
        
        queue.inDatabase { (db) in
            
            guard let resultSet = db?.executeQuery(sql, withArgumentsIn: []) else
            {
                return
            }
            //逐行遍历结果集合
            while resultSet.next()
            {
                //列数
                let colCount = resultSet.columnCount()
                //遍历所有列
                for col in 0..<colCount
                {
                    //列名
//                    let name = resultSet.columnName(for: col)
//                    //列值
//                    let value = resultSet.object(forColumnIndex: col)
                    guard let name = resultSet.columnName(for: col),
                          let value = resultSet.object(forColumnIndex: col) else
                    {
                        continue
                    }
                    result.append([name : value as AnyObject])
                }
            }
        }
        
        return result
    }
}


extension CPJSQLiteManager
{
    /// 从数据库加载微博数据数组
    ///
    /// - Parameters:
    ///   - userId: 当前登录用户ID
    ///   - since_id: 返回比since_id大的微博
    ///   - max_id: 返回比max_id小的微博
    /// - Returns: 将数据库中的status二进制数据反序列化，转换为字典
    func loadStatus(userId: String,since_id: Int64 = 0,max_id: Int64 = 0) -> [[String: AnyObject]]
    {
        var sql = "SELECT statusId, userId, status FROM Main_Weibo\n"
        sql += "WHERE userId = \(userId)\n"
        
        //上拉／下拉，都是针对同一个ID进行操作
        if since_id > 0
        {
            sql += "AND statusId > \(since_id)\n"
        }
        else if max_id > 0
        {
            sql += "AND statusId < \(max_id)\n"
        }
        
        sql += "ORDER BY statusId DESC LIMIT 20;"
        
        print("sql = \(sql)")
        
        //执行sql
        let array = queryTable(sql: sql)
        
//        print(array)
        
        //遍历数组，将数组中的status反序列化 ->字典的数组
        var result = [[String: AnyObject]]()
        
        for dict in array
        {
            guard let jsonData = dict["status"] as? Data,
                  let json = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: AnyObject] else
            {
                continue
            }
            result.append(json ?? [:])

        }
        
        return result
    }
}
